#-------------------------------------------------------------------------------
# Copyright (c) 2012 Huseyin BIYIK.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the GNU Public License v3.0
# which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/gpl.html
# 
# Contributors:
#     Huseyin BIYIK - initial API and implementation
#-------------------------------------------------------------------------------
import socket, threading, random, uuid, time, logging, select
from messages import *
from audio import *
from call import *
from defs import logger


debug = 0

class stack(threading.Thread):
	def __init__(self):
		threading.Thread.__init__(self)
		self.ai = audio_interface()
		self.ai.activate()
		self.state = "idle"
		self.is_listenning = False
		self.addr = ''
		self.port = random.randint(10000, 20000)
		self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.sock.settimeout(1)
		self.sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.calls = {} #{callid:(destip,destport)}
		self.bandwidth = {}
		self.loopback = False
		self.cm = manager()
		self.isclosing = False

		self.start()

	class t_signal(Exception):
	    pass
		
	def listen(self):
		if not self.is_listenning:
			self.sock.bind((self.addr, self.port))
			self.addr, self.port = self.sock.getsockname()
			logger.info("Started listening on %s:%d", self.addr, self.port)
			self.is_listenning = True

	def connectrequest(self, localip, localport, destip, destport):
		self.state = "calling"
		msg = messages().types[0]
		args = [localip, localport, destip, destport]
		msg = messages().to_str(msg, args)
		self.sock_send.sendto(msg, (destip, int(destport)))
		logger.info("Requested connection to %s:%d", destip, destport)
	
	def connectaccept(self, localip, localport, destip, destport, callid):
		self.state = "busy"
		msg = messages().types[1]
		args = [localip, localport, destip, destport, callid]
		msg = messages().to_str(msg, args)
		self.sock_send.sendto(msg, (destip, int(destport)))
		logger.info("Accepted connection from %s:%d", destip, destport)

	def connectdecline(self, localip, localport, destip, destport):
		self.state = "idle"
		msg = messages().types[1]
		args = [localip, localport, destip, destport]
		msg = messages().to_str(msg, args)
		self.sock_send.sendto(msg, (destip, int(destport)))
		logger.info("Declined connection from %s:%d", destip, destport)

	def senddata(self, callid, localip, localport, destip, destport, dtype, payload):
		msg = messages().types[4]
		args = [callid, localip, localport, destip, destport, dtype, payload]
		msg = messages().to_str(msg, args)
		self.sock_send.sendto(msg, (destip, int(destport)))
		logger.debug("%s data sent to %s:%d", dtype, destip, destport)

	def run(self):
		bandwidth = {}
		while True:
			if self.isclosing:
				break
			if self.is_listenning:
				try:
					r, _, _ = select.select([self.sock], [], [])
					if r:
						data, addr = self.sock.recvfrom(udp_packet_size) # buffer size is 1024 bytes
				except :
					logger.debug("Error occured with receiving socket %s", str(e))
					print "wever"
					continue
				logger.debug("Client received message: %s", data)
				msg, args = messages().from_str(data)
				## if connection requested
				if msg == messages().types[0]:
					self.state = "calling"
					remoteip = args[0] #destip
					remoteport = args[1] #destport
					if  self.port == int(args[3]):
						callid = str(uuid.uuid1()) #unique call id
						self.connectaccept(self.addr, self.port, remoteip, remoteport, callid)
					else:
						self.connectdecline(self.addr, self.port, remoteip, remoteport)
						logger.debug("Client declined call from: %s:%s ", remoteip, remoteport)
						self.state = "idle"

				## if connection is accepted
				if msg == messages().types[1]:
					self.state = "busy"
					callid = args[4]
					if self.cm.add_call(callid, args[2], args[3], args[0], args[1]):
						self.senddata()
					logger.debug("Call '%s' from %s:%s accepted", callid, args[2], args[3])


				## if connection is declined
				if msg == messages().types[2]:
					if self.cm.remove_call(callid):
						logger.debug("Call '%s' declined from %s:%s", callid, args[0], args[1])


				## if data is received
				if msg == messages().types[4]:
					callid = args[0]
					dtype = args[5]
					if self.cm.is_call(callid):
						if dtype == "audio":
							self.ai.put(args[6])
						if dtype == "bandwidth":
							sequence = args[6]
							bandwidth = self.handle_data_bandwidth(callid, bandwidth, sequence)

						logger.debug("%s data received from call '%s': ", dtype, callid)

					if self.loopback:
							self.senddata(args[0], args[3], args[4], args[1], args[2], args[5], args[6])
							logger.debug("Data loopbacked for call '%s'", str(callid))

				if not self.loopback:
					for callid in self.cm.get_calls():
						if self.cm.is_call(callid):
							mic = self.ai.get()
#							for dtype in self.call.get_call_data(callid,None):
							if self.cm.get_call_data(callid, 'audio'):
								self.send_data([callid], 'audio', mic)
						else:
							self.cm.remove_call(callid)
#				if msg == messages().types[5]:

	def send_data(self, callids, dtype, payload):
		for callid in callids:
			call = self.cm.get_call(callid)
			if not call is None:
				self.senddata(callid, call.fromaddr, call.fromport, call.toaddr, call.toport, dtype, payload)

	def handle_data_bandwidth(self, callid, bandwidth, sequence):
#		self.send_data([callid],"bandwidth",sequence)
#		if not callid in bandwidth.keys():
#			bandwidth[callid]={"times":[],"sequences":[]}
		call = self.cm.get_call(callid)
		if not call is None:
			bandwidth = call.bandwidth
			if (len(bandwidth.keys()) != bw_seq_count and not sequence in bandwidth.keys()):
				bandwidth[sequence] = time.time()
			if (len(bandwidth.keys()) == bw_seq_count):
				#self.sendbandwidthreport(callid)
				prvs = None
				differences = []
				for sequence in sorted(bandwidth.keys()):
					if not prvs is None and prvs < bandwidth[sequence]:
						differences.append(bandwidth[sequence] - prvs)
					prvs = bandwidth[sequence]
				call.differences_down = differences
				logger.info("Avg downspeed for call '%s' : %d kbps", callid, call.get_downspeed())
				logger.info("Avg downlatency for call '%s' : %d ms", callid, call.get_downjitter())

				bandwidth = {}
			call.bandwidth = bandwidth

	def add_call_manual(self, callid, fromaddr, fromport, toaddr, toport):
		self.cm.add_call(callid, fromaddr, fromport, toaddr, toport)
	
	def close(self):
		print "1"
		self.isclosing = True
		print self.sock.shutdown(socket.SHUT_WR)
		print self.sock_send.shutdown(socket.SHUT_WR)
		self.ai.close()
		print "2"
		print threading.enumerate()
		threading.Thread.join(self)
		return True
